# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::Bridge, feature_category: :continuous_integration do
  let_it_be(:project, refind: true) { create(:project, :repository, :in_group) }
  let_it_be(:target_project) { create(:project, name: 'project', namespace: create(:namespace, name: 'my')) }
  let_it_be(:pipeline) { create(:ci_pipeline, project: project) }

  before_all do
    create(:ci_pipeline_variable, pipeline: pipeline, key: 'PVAR1', value: 'PVAL1')
  end

  let(:bridge) do
    create(:ci_bridge, :variables, status: :created, options: options, pipeline: pipeline)
  end

  let(:options) do
    { trigger: { project: 'my/project', branch: 'master' } }
  end

  it 'has one sourced pipeline' do
    expect(bridge).to have_one(:sourced_pipeline)
  end

  it_behaves_like 'has ID tokens', :ci_bridge

  it_behaves_like 'a retryable job'

  it_behaves_like 'a deployable job' do
    let(:job) { bridge }
  end

  it 'has one downstream pipeline' do
    expect(bridge).to have_one(:sourced_pipeline)
    expect(bridge).to have_one(:downstream_pipeline)
  end

  describe '#retryable?' do
    let(:bridge) { create(:ci_bridge, :success) }

    it 'returns true' do
      expect(bridge.retryable?).to eq(true)
    end
  end

  context 'when there is a pipeline loop detected' do
    let(:bridge) { create(:ci_bridge, :failed, failure_reason: :pipeline_loop_detected) }

    it 'returns false' do
      expect(bridge.failure_reason).to eq('pipeline_loop_detected')
      expect(bridge.retryable?).to eq(false)
    end
  end

  context 'when the pipeline depth has reached the max descendents' do
    let(:bridge) { create(:ci_bridge, :failed, failure_reason: :reached_max_descendant_pipelines_depth) }

    it 'returns false' do
      expect(bridge.failure_reason).to eq('reached_max_descendant_pipelines_depth')
      expect(bridge.retryable?).to eq(false)
    end
  end

  describe '#tags' do
    it 'only has a bridge tag' do
      expect(bridge.tags).to eq [:bridge]
    end
  end

  describe '#detailed_status' do
    let(:user) { create(:user) }
    let(:status) { bridge.detailed_status(user) }

    it 'returns detailed status object' do
      expect(status).to be_a Gitlab::Ci::Status::Created
    end
  end

  describe '#scoped_variables' do
    it 'returns a hash representing variables' do
      variables = %w[
        CI_JOB_NAME CI_JOB_NAME_SLUG CI_JOB_STAGE CI_COMMIT_SHA
        CI_COMMIT_SHORT_SHA CI_COMMIT_BEFORE_SHA CI_COMMIT_REF_NAME
        CI_COMMIT_REF_SLUG CI_PROJECT_ID CI_PROJECT_NAME CI_PROJECT_PATH
        CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PROJECT_ROOT_NAMESPACE
        CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE
        CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_COMMIT_REF_PROTECTED
        CI_COMMIT_TIMESTAMP CI_COMMIT_AUTHOR
      ]

      expect(bridge.scoped_variables.map { |v| v[:key] }).to include(*variables)
    end

    context 'when bridge has dependency which has dotenv variable' do
      let(:test) { create(:ci_build, pipeline: pipeline, stage_idx: 0) }
      let(:bridge) { create(:ci_bridge, pipeline: pipeline, stage_idx: 1, options: { dependencies: [test.name] }) }

      let!(:job_variable) { create(:ci_job_variable, :dotenv_source, job: test) }

      it 'includes inherited variable' do
        expect(bridge.scoped_variables.to_hash).to include(job_variable.key => job_variable.value)
      end
    end
  end

  describe 'state machine transitions' do
    context 'when bridge points towards downstream' do
      %i[created manual].each do |status|
        it "schedules downstream pipeline creation when the status is #{status}" do
          bridge.status = status

          bridge.enqueue!

          expect(::Ci::CreateDownstreamPipelineWorker.jobs.last['args']).to eq([bridge.id])
        end
      end

      it "schedules downstream pipeline creation when the status is waiting for resource" do
        bridge.status = :waiting_for_resource

        bridge.enqueue_waiting_for_resource!

        expect(::Ci::CreateDownstreamPipelineWorker.jobs.last['args']).to match_array([bridge.id])
      end

      it 'raises error when the status is failed' do
        bridge.status = :failed

        expect { bridge.enqueue! }.to raise_error(StateMachines::InvalidTransition)
      end
    end
  end

  describe '#inherit_status_from_downstream!' do
    let(:downstream_pipeline) { build(:ci_pipeline, status: downstream_status) }

    before do
      bridge.status = 'pending'
      create(:ci_sources_pipeline, pipeline: downstream_pipeline, source_job: bridge)
    end

    subject { bridge.inherit_status_from_downstream!(downstream_pipeline) }

    context 'when status is not supported' do
      (::Ci::Pipeline::AVAILABLE_STATUSES - ::Ci::Pipeline::COMPLETED_STATUSES).map(&:to_s).each do |status|
        context "when status is #{status}" do
          let(:downstream_status) { status }

          it 'returns false' do
            expect(subject).to eq(false)
          end

          it 'does not change the bridge status' do
            expect { subject }.not_to change { bridge.status }.from('pending')
          end
        end
      end
    end

    context 'when status is supported' do
      using RSpec::Parameterized::TableSyntax

      where(:downstream_status, :upstream_status) do
        [
          %w[success success],
          %w[canceled canceled],
          %w[failed failed],
          %w[skipped failed]
        ]
      end

      with_them do
        it 'inherits the downstream status' do
          expect { subject }.to change { bridge.status }.from('pending').to(upstream_status)
        end
      end
    end
  end

  describe '#dependent?' do
    subject { bridge.dependent? }

    context 'when bridge has strategy depend' do
      let(:options) { { trigger: { project: 'my/project', strategy: 'depend' } } }

      it { is_expected.to be true }
    end

    context 'when bridge does not have strategy depend' do
      it { is_expected.to be false }
    end
  end

  describe '#yaml_variables' do
    it 'returns YAML variables' do
      expect(bridge.yaml_variables)
        .to include(key: 'BRIDGE', value: 'cross', public: true)
    end
  end

  describe '#downstream_variables' do
    # A new pipeline needs to be created in each test.
    # The pipeline #variables_builder is memoized. The builder internally also memoizes variables.
    # Having pipeline in a let_it_be might lead to flaky tests
    # because a test might expect new variables but the variables builder does not
    # return the new variables due to memoized results from previous tests.
    let(:pipeline) { create(:ci_pipeline, project: project) }

    subject(:downstream_variables) { bridge.downstream_variables }

    it 'returns variables that are going to be passed downstream' do
      expect(bridge.downstream_variables)
        .to contain_exactly(key: 'BRIDGE', value: 'cross')
    end

    context 'when using variables interpolation' do
      let(:yaml_variables) do
        [
          {
            key: 'EXPANDED',
            value: '$BRIDGE-bridge',
            public: true
          },
          {
            key: 'UPSTREAM_CI_PIPELINE_ID',
            value: '$CI_PIPELINE_ID',
            public: true
          },
          {
            key: 'UPSTREAM_CI_PIPELINE_URL',
            value: '$CI_PIPELINE_URL',
            public: true
          }
        ]
      end

      before do
        bridge.yaml_variables.concat(yaml_variables)
      end

      it 'correctly expands variables with interpolation' do
        expanded_values = pipeline
          .persisted_variables
          .to_hash
          .transform_keys { |key| "UPSTREAM_#{key}" }
          .map { |key, value| { key: key, value: value } }
          .push(key: 'EXPANDED', value: 'cross-bridge')

        expect(bridge.downstream_variables)
          .to match(a_collection_including(*expanded_values))
      end
    end

    context 'when using variables interpolation on file variables' do
      let(:yaml_variables) do
        [
          {
            key: 'EXPANDED_FILE',
            value: '$TEST_FILE_VAR'
          }
        ]
      end

      before do
        bridge.yaml_variables = yaml_variables
        create(:ci_variable, :file, project: bridge.pipeline.project, key: 'TEST_FILE_VAR', value: 'test-file-value')
      end

      it 'does not expand file variable and forwards the file variable' do
        expected_vars = [
          { key: 'EXPANDED_FILE', value: '$TEST_FILE_VAR' },
          { key: 'TEST_FILE_VAR', value: 'test-file-value', variable_type: :file }
        ]

        expect(bridge.downstream_variables).to contain_exactly(*expected_vars)
      end

      context 'and feature flag is disabled' do
        before do
          stub_feature_flags(ci_prevent_file_var_expansion_downstream_pipeline: false)
        end

        it 'expands the file variable' do
          expect(bridge.downstream_variables).to contain_exactly({ key: 'EXPANDED_FILE', value: 'test-file-value' })
        end
      end
    end

    context 'when recursive interpolation has been used' do
      before do
        bridge.yaml_variables = [{ key: 'EXPANDED', value: '$EXPANDED', public: true }]
      end

      it 'does not expand variable recursively' do
        expect(bridge.downstream_variables)
          .to contain_exactly(key: 'EXPANDED', value: '$EXPANDED')
      end
    end

    context 'forward variables' do
      using RSpec::Parameterized::TableSyntax

      where(:yaml_variables, :pipeline_variables, :variables) do
        nil   | nil   | %w[BRIDGE]
        nil   | false | %w[BRIDGE]
        nil   | true  | %w[BRIDGE PVAR1]
        false | nil   | %w[]
        false | false | %w[]
        false | true  | %w[PVAR1]
        true  | nil   | %w[BRIDGE]
        true  | false | %w[BRIDGE]
        true  | true  | %w[BRIDGE PVAR1]
      end

      with_them do
        let(:options) do
          {
            trigger: {
              project: 'my/project',
              branch: 'master',
              forward: { yaml_variables: yaml_variables,
                         pipeline_variables: pipeline_variables }.compact
            }
          }
        end

        before do
          create(:ci_pipeline_variable, pipeline: pipeline, key: 'PVAR1', value: 'PVAL1')
        end

        it 'returns variables according to the forward value' do
          expect(bridge.downstream_variables.map { |v| v[:key] }).to contain_exactly(*variables)
        end
      end

      context 'when sending a variable via both yaml and pipeline' do
        let(:options) do
          { trigger: { project: 'my/project', forward: { pipeline_variables: true } } }
        end

        before do
          bridge.yaml_variables = [{ key: 'SHARED_KEY', value: 'old_value' }]
          create(:ci_pipeline_variable, pipeline: pipeline, key: 'SHARED_KEY', value: 'new value')
        end

        it 'uses the pipeline variable' do
          expect(bridge.downstream_variables).to contain_exactly({ key: 'SHARED_KEY', value: 'new value' })
        end
      end

      context 'when sending a file variable from pipeline variable' do
        let(:options) do
          { trigger: { project: 'my/project', forward: { pipeline_variables: true } } }
        end

        before do
          bridge.yaml_variables = [{ key: 'FILE_VAR', value: 'old_value' }]
          create(:ci_pipeline_variable, :file, pipeline: pipeline, key: 'FILE_VAR', value: 'new value')
        end

        # The current behaviour forwards the file variable as an environment variable.
        # TODO: decide whether to forward as a file var in https://gitlab.com/gitlab-org/gitlab/-/issues/416334
        it 'forwards the pipeline file variable' do
          expect(bridge.downstream_variables).to contain_exactly({ key: 'FILE_VAR', value: 'new value' })
        end
      end

      context 'when a pipeline variable interpolates a scoped file variable' do
        let(:options) do
          { trigger: { project: 'my/project', forward: { pipeline_variables: true } } }
        end

        before do
          bridge.yaml_variables = [{ key: 'YAML_VAR', value: '$PROJECT_FILE_VAR' }]

          create(:ci_variable, :file, project: pipeline.project, key: 'PROJECT_FILE_VAR', value: 'project file')
          create(:ci_pipeline_variable, pipeline: pipeline, key: 'FILE_VAR', value: '$PROJECT_FILE_VAR')
        end

        it 'does not expand the scoped file variable and forwards the file variable' do
          expected_vars = [
            { key: 'FILE_VAR', value: '$PROJECT_FILE_VAR' },
            { key: 'YAML_VAR', value: '$PROJECT_FILE_VAR' },
            { key: 'PROJECT_FILE_VAR', value: 'project file', variable_type: :file }
          ]

          expect(bridge.downstream_variables).to contain_exactly(*expected_vars)
        end

        context 'and feature flag is disabled' do
          before do
            stub_feature_flags(ci_prevent_file_var_expansion_downstream_pipeline: false)
          end

          it 'expands the file variable' do
            expected_vars = [
              { key: 'FILE_VAR', value: 'project file' },
              { key: 'YAML_VAR', value: 'project file' }
            ]

            expect(bridge.downstream_variables).to contain_exactly(*expected_vars)
          end
        end
      end

      context 'when the pipeline runs from a pipeline schedule' do
        let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) }
        let(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) }

        let(:options) do
          { trigger: { project: 'my/project', forward: { pipeline_variables: true } } }
        end

        before do
          pipeline_schedule.variables.create!(key: 'schedule_var_key', value: 'schedule var value')
        end

        it 'adds the schedule variable' do
          expected_vars = [
            { key: 'BRIDGE', value: 'cross' },
            { key: 'schedule_var_key', value: 'schedule var value' }
          ]

          expect(bridge.downstream_variables).to contain_exactly(*expected_vars)
        end
      end
    end

    context 'when sending a file variable from pipeline schedule' do
      let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) }
      let(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) }

      let(:options) do
        { trigger: { project: 'my/project', forward: { pipeline_variables: true } } }
      end

      before do
        bridge.yaml_variables = []
        pipeline_schedule.variables.create!(key: 'schedule_var_key', value: 'schedule var value', variable_type: :file)
      end

      # The current behaviour forwards the file variable as an environment variable.
      # TODO: decide whether to forward as a file var in https://gitlab.com/gitlab-org/gitlab/-/issues/416334
      it 'forwards the schedule file variable' do
        expect(bridge.downstream_variables).to contain_exactly({ key: 'schedule_var_key', value: 'schedule var value' })
      end
    end

    context 'when a pipeline schedule variable interpolates a scoped file variable' do
      let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) }
      let(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) }

      let(:options) do
        { trigger: { project: 'my/project', forward: { pipeline_variables: true } } }
      end

      before do
        bridge.yaml_variables = []
        create(:ci_variable, :file, project: pipeline.project, key: 'PROJECT_FILE_VAR', value: 'project file')
        pipeline_schedule.variables.create!(key: 'schedule_var_key', value: '$PROJECT_FILE_VAR')
      end

      it 'does not expand the scoped file variable and forwards the file variable' do
        expected_vars = [
          { key: 'schedule_var_key', value: '$PROJECT_FILE_VAR' },
          { key: 'PROJECT_FILE_VAR', value: 'project file', variable_type: :file }
        ]

        expect(bridge.downstream_variables).to contain_exactly(*expected_vars)
      end

      context 'and feature flag is disabled' do
        before do
          stub_feature_flags(ci_prevent_file_var_expansion_downstream_pipeline: false)
        end

        it 'expands the file variable' do
          expect(bridge.downstream_variables).to contain_exactly({ key: 'schedule_var_key', value: 'project file' })
        end
      end
    end

    context 'when using raw variables' do
      let(:options) do
        {
          trigger: {
            project: 'my/project',
            branch: 'master',
            forward: { yaml_variables: true,
                       pipeline_variables: true }.compact
          }
        }
      end

      let(:yaml_variables) do
        [
          {
            key: 'VAR6',
            value: 'value6 $VAR1'
          },
          {
            key: 'VAR7',
            value: 'value7 $VAR1',
            raw: true
          }
        ]
      end

      let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) }
      let(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) }

      before do
        create(:ci_pipeline_variable, pipeline: pipeline, key: 'VAR1', value: 'value1')
        create(:ci_pipeline_variable, pipeline: pipeline, key: 'VAR2', value: 'value2 $VAR1')
        create(:ci_pipeline_variable, pipeline: pipeline, key: 'VAR3', value: 'value3 $VAR1', raw: true)

        pipeline_schedule.variables.create!(key: 'VAR4', value: 'value4 $VAR1')
        pipeline_schedule.variables.create!(key: 'VAR5', value: 'value5 $VAR1', raw: true)

        bridge.yaml_variables.concat(yaml_variables)
      end

      it 'expands variables according to their raw attributes' do
        expect(downstream_variables).to contain_exactly(
          { key: 'BRIDGE', value: 'cross' },
          { key: 'VAR1', value: 'value1' },
          { key: 'VAR2', value: 'value2 value1' },
          { key: 'VAR3', value: 'value3 $VAR1', raw: true },
          { key: 'VAR4', value: 'value4 value1' },
          { key: 'VAR5', value: 'value5 $VAR1', raw: true },
          { key: 'VAR6', value: 'value6 value1' },
          { key: 'VAR7', value: 'value7 $VAR1', raw: true }
        )
      end
    end
  end

  describe '#variables' do
    it 'returns bridge scoped variables and pipeline persisted variables' do
      expect(bridge.variables.to_hash)
        .to eq(bridge.scoped_variables.concat(bridge.pipeline.persisted_variables).to_hash)
    end
  end

  describe '#pipeline_variables' do
    it 'returns the pipeline variables' do
      expect(bridge.pipeline_variables).to eq(bridge.pipeline.variables)
    end
  end

  describe '#pipeline_schedule_variables' do
    context 'when pipeline is on a schedule' do
      let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
      let(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) }

      it 'returns the pipeline schedule variables' do
        create(:ci_pipeline_schedule_variable, key: 'FOO', value: 'foo', pipeline_schedule: pipeline.pipeline_schedule)

        pipeline_schedule_variables = bridge.reload.pipeline_schedule_variables
        expect(pipeline_schedule_variables).to match_array([have_attributes({ key: 'FOO', value: 'foo' })])
      end
    end

    context 'when pipeline is not on a schedule' do
      it 'returns empty array' do
        expect(bridge.pipeline_schedule_variables).to eq([])
      end
    end
  end

  describe '#forward_yaml_variables?' do
    using RSpec::Parameterized::TableSyntax

    where(:forward, :result) do
      true | true
      false | false
      nil | true
    end

    with_them do
      let(:options) do
        {
          trigger: {
            project: 'my/project',
            branch: 'master',
            forward: { yaml_variables: forward }.compact
          }
        }
      end

      let(:bridge) { build(:ci_bridge, options: options) }

      it { expect(bridge.forward_yaml_variables?).to eq(result) }
    end
  end

  describe '#forward_pipeline_variables?' do
    using RSpec::Parameterized::TableSyntax

    where(:forward, :result) do
      true | true
      false | false
      nil | false
    end

    with_them do
      let(:options) do
        {
          trigger: {
            project: 'my/project',
            branch: 'master',
            forward: { pipeline_variables: forward }.compact
          }
        }
      end

      let(:bridge) { build(:ci_bridge, options: options) }

      it { expect(bridge.forward_pipeline_variables?).to eq(result) }
    end
  end

  describe 'metadata support' do
    it 'reads YAML variables from metadata' do
      expect(bridge.yaml_variables).not_to be_empty
      expect(bridge.metadata).to be_a Ci::BuildMetadata
      expect(bridge.read_attribute(:yaml_variables)).to be_nil
      expect(bridge.metadata.config_variables).to be bridge.yaml_variables
    end

    it 'reads options from metadata' do
      expect(bridge.options).not_to be_empty
      expect(bridge.metadata).to be_a Ci::BuildMetadata
      expect(bridge.read_attribute(:options)).to be_nil
      expect(bridge.metadata.config_options).to be bridge.options
    end
  end

  describe '#triggers_child_pipeline?' do
    subject { bridge.triggers_child_pipeline? }

    context 'when bridge defines a downstream YAML' do
      let(:options) do
        {
          trigger: {
            include: 'path/to/child.yml'
          }
        }
      end

      it { is_expected.to be_truthy }
    end

    context 'when bridge does not define a downstream YAML' do
      let(:options) do
        {
          trigger: {
            project: project.full_path
          }
        }
      end

      it { is_expected.to be_falsey }
    end
  end

  describe '#yaml_for_downstream' do
    subject { bridge.yaml_for_downstream }

    context 'when bridge defines a downstream YAML' do
      let(:options) do
        {
          trigger: {
            include: 'path/to/child.yml'
          }
        }
      end

      let(:yaml) do
        <<~EOY
          ---
          include: path/to/child.yml
        EOY
      end

      it { is_expected.to eq yaml }
    end

    context 'when bridge does not define a downstream YAML' do
      let(:options) { {} }

      it { is_expected.to be_nil }
    end
  end

  describe '#downstream_project_path' do
    context 'when trigger is defined' do
      context 'when using variable expansion' do
        let(:options) { { trigger: { project: 'my/$BRIDGE/project' } } }

        it 'correctly expands variables' do
          expect(bridge.downstream_project_path).to eq('my/cross/project')
        end
      end
    end
  end

  describe '#target_ref' do
    context 'when trigger is defined' do
      it 'returns a ref name' do
        expect(bridge.target_ref).to eq 'master'
      end

      context 'when using variable expansion' do
        let(:options) { { trigger: { project: 'my/project', branch: '$BRIDGE-master' } } }

        it 'correctly expands variables' do
          expect(bridge.target_ref).to eq('cross-master')
        end
      end
    end

    context 'when trigger does not have project defined' do
      let(:options) { nil }

      it 'returns nil' do
        expect(bridge.target_ref).to be_nil
      end
    end
  end

  describe '#play' do
    let(:downstream_project) { create(:project) }
    let(:user) { create(:user) }
    let(:bridge) { create(:ci_bridge, :playable, pipeline: pipeline, downstream: downstream_project) }

    subject { bridge.play(user) }

    before do
      project.add_maintainer(user)
      downstream_project.add_maintainer(user)
    end

    it 'enqueues the bridge' do
      subject

      expect(bridge).to be_pending
    end
  end

  describe '#playable?' do
    context 'when bridge is a manual action' do
      subject { build_stubbed(:ci_bridge, :manual).playable? }

      it { is_expected.to be_truthy }
    end

    context 'when build is not a manual action' do
      subject { build_stubbed(:ci_bridge, :created).playable? }

      it { is_expected.to be_falsey }
    end
  end

  describe '#action?' do
    context 'when bridge is a manual action' do
      subject { build_stubbed(:ci_bridge, :manual).action? }

      it { is_expected.to be_truthy }
    end

    context 'when build is not a manual action' do
      subject { build_stubbed(:ci_bridge, :created).action? }

      it { is_expected.to be_falsey }
    end
  end

  describe '#dependency_variables' do
    subject { bridge.dependency_variables }

    context 'when downloading from previous stages' do
      let!(:prepare1) { create(:ci_build, name: 'prepare1', pipeline: pipeline, stage_idx: 0) }
      let!(:bridge) { create(:ci_bridge, pipeline: pipeline, stage_idx: 1) }

      let!(:job_variable_1) { create(:ci_job_variable, :dotenv_source, job: prepare1) }
      let!(:job_variable_2) { create(:ci_job_variable, job: prepare1) }

      it 'inherits only dependent variables' do
        expect(subject.to_hash).to eq(job_variable_1.key => job_variable_1.value)
      end
    end

    context 'when using needs' do
      let!(:prepare1) { create(:ci_build, name: 'prepare1', pipeline: pipeline, stage_idx: 0) }
      let!(:prepare2) { create(:ci_build, name: 'prepare2', pipeline: pipeline, stage_idx: 0) }
      let!(:prepare3) { create(:ci_build, name: 'prepare3', pipeline: pipeline, stage_idx: 0) }
      let!(:bridge) do
        create(
          :ci_bridge,
          pipeline: pipeline,
          stage_idx: 1,
          scheduling_type: 'dag',
          needs_attributes: [{ name: 'prepare1', artifacts: true }, { name: 'prepare2', artifacts: false }]
        )
      end

      let!(:job_variable_1) { create(:ci_job_variable, :dotenv_source, job: prepare1) }
      let!(:job_variable_2) { create(:ci_job_variable, :dotenv_source, job: prepare2) }
      let!(:job_variable_3) { create(:ci_job_variable, :dotenv_source, job: prepare3) }

      it 'inherits only needs with artifacts variables' do
        expect(subject.to_hash).to eq(job_variable_1.key => job_variable_1.value)
      end
    end
  end

  describe 'metadata partitioning', :ci_partitionable do
    let(:pipeline) { create(:ci_pipeline, project: project, partition_id: ci_testing_partition_id) }

    let(:bridge) do
      build(:ci_bridge, pipeline: pipeline)
    end

    it 'creates the metadata record and assigns its partition' do
      # the factory doesn't use any metadatable setters by default
      # so the record will be initialized by the before_validation callback
      expect(bridge.metadata).to be_nil

      expect(bridge.save!).to be_truthy

      expect(bridge.metadata).to be_present
      expect(bridge.metadata).to be_valid
      expect(bridge.metadata.partition_id).to eq(ci_testing_partition_id)
    end
  end

  describe '#deployment_job?' do
    subject { bridge.deployment_job? }

    it { is_expected.to eq(false) }
  end
end
